package com.vois.jack.btmgr.blebase;

import android.annotation.TargetApi;
import android.bluetooth.BluetoothAdapter;
import android.bluetooth.BluetoothDevice;
import android.bluetooth.BluetoothGatt;
import android.bluetooth.BluetoothGattCallback;
import android.bluetooth.BluetoothGattCharacteristic;
import android.bluetooth.BluetoothGattDescriptor;
import android.bluetooth.BluetoothProfile;
import android.bluetooth.le.ScanCallback;
import android.bluetooth.le.ScanFilter;
import android.bluetooth.le.ScanResult;
import android.bluetooth.le.ScanSettings;
import android.os.Build;
import android.os.Bundle;
import android.os.Message;
import android.os.ParcelUuid;
import android.util.Log;

import com.vois.jack.btmgr.classicbase.BtFsmMsg;
import com.vois.jack.btmgr.common.State;
import com.vois.jack.btmgr.common.StateMachine;
import com.vois.jack.btmgr.util.Logger;
import com.vois.jack.btmgr.util.TimeoutMonitor;

import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Timer;
import java.util.TimerTask;
import java.util.UUID;

public class BleDeviceFsm extends StateMachine {
    private Logger logger = Logger.getLogger(BleDeviceFsm.class);
    public final static String EXTRA_ACTION_VALUE        = "action_value";
    public final static String EXTRA_SERVICE_UUID        = "service_uuid";
    public final static String EXTRA_ENABLE_VALUE        = "enable_value";
    public final static String EXTRA_RAW_DATA            = "raw_data";
    public final static String EXTRA_CHARACTERISTIC_UUID = "characteristic_uuid";
    public final static String EXTRA_DESCRIPTOR_UUID     = "descriptor_uuid";
    public final static String EXTRA_RSSI_VALUE          = "rssi_value";
    public final static String EXTRA_MTU_VALUE           = "mtu_value";
    public final static String EXTRA_CHARACTERISTIC      = "characteristic";
    public final static String EXTRA_DESCRIPTOR          = "descriptor";
    public final static String EXTRA_VALUE_DATA          = "value_data";

    public static final int STATE_STARTING             =   0x01;
    public static final int STATE_TRYING               =   0x02;
    public static final int STATE_FOUND                =   0x03;
    public static final int STATE_STOP                 =   0x04;
    public static final int STATE_FAIL                 =   0x05;

    public static final int DISCONNECTED_IN_PURPOSE    =   0x01;
    public static final int DISCONNECTED_ACCIDENT      =   0x02;

    private final int BALANCED_SCAN_MODE    =  0x00;
    private final int LOW_LATENCY_SCAN_MODE =  0x01;
    private final int LOW_POWER_SCAN_MODE   =  0x02;

    private BleDevice bleDevice;
    private InitState initState;
    private ScanningState scanningState;
    private ConnectingState connectingState;
    private ConnectedState connectedState;
    private VerifyingState verifyingState;
    private ReadyState readyState;
    private DisconnectedState disconnectedState;
    private StoppingState stoppingState;
    private BluetoothGatt bluetoothGatt;
    private int currentFullConnectTry;
    private int currentFullConnectTried;
    private boolean disconnectedByPurpose;
    private Queue<BleAction> bleActionQueue;
    private int connectSate;
    private boolean isWorking;
    private boolean isFullReconnecting;
    private int curScanMode = LOW_LATENCY_SCAN_MODE;
    private boolean isConnected;
    private boolean stopScanning;

    private BluetoothGattCallback bluetoothGattCallback = new BluetoothGattCallback() {
        @Override
        public void onPhyUpdate(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {

        }

        @Override
        public void onPhyRead(BluetoothGatt gatt, int txPhy, int rxPhy, int status) {

        }

        @Override
        public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) {
            if (newState == BluetoothGatt.STATE_DISCONNECTED) {
                BleHelper.getInstance().refreshDeviceCache(gatt);
            }

            connectSate = newState;
            sendMessage(BleFsmMsg.BLE_FSM_CONNECTION_STATE_CHANGED_MSG.ordinal(), newState, status);
        }

        @Override
        public void onServicesDiscovered(BluetoothGatt gatt, int status) {
            logger.d( "onServicesDiscovered: status:" + status);
            sendMessage(BleFsmMsg.BLE_FSM_DISCOVERY_SERVICE_FINISH_MSG.ordinal(), status);
        }

        @Override
        public void onCharacteristicRead(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            logger.d( "onCharacteristicRead: status:" + status);
            Bundle bundle = new Bundle();
            byte[] data = new byte[characteristic.getValue().length];
            System.arraycopy(characteristic.getValue(), 0, data, 0, characteristic.getValue().length);
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_READ_DATA.ordinal());
            bundle.putParcelable(EXTRA_SERVICE_UUID, new ParcelUuid(characteristic.getService().getUuid()));
            bundle.putParcelable(EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristic.getUuid()));
            bundle.putByteArray(EXTRA_VALUE_DATA, data);
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }

        @Override
        public void onCharacteristicWrite(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic, int status) {
            logger.d( "onCharacteristicWrite: status:" + status);
            Bundle bundle = new Bundle();
            byte[] data = new byte[characteristic.getValue().length];
            System.arraycopy(characteristic.getValue(), 0, data, 0, characteristic.getValue().length);
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_WRITE_DATA.ordinal());
            bundle.putParcelable(EXTRA_SERVICE_UUID, new ParcelUuid(characteristic.getService().getUuid()));
            bundle.putParcelable(EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristic.getUuid()));
            bundle.putByteArray(EXTRA_VALUE_DATA, data);
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }

        @Override
        public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) {
            //logger.d( "onCharacteristicChanged: UUID:" + characteristic.getUuid().toString());
            Bundle bundle = new Bundle();
            byte[] data = new byte[characteristic.getValue().length];
            System.arraycopy(characteristic.getValue(), 0, data, 0, characteristic.getValue().length);
            bundle.putParcelable(EXTRA_SERVICE_UUID, new ParcelUuid(characteristic.getService().getUuid()));
            bundle.putParcelable(EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(characteristic.getUuid()));
            bundle.putByteArray(EXTRA_VALUE_DATA, characteristic.getValue());
            bundle.putByteArray(EXTRA_VALUE_DATA, data);
            sendMessage(BleFsmMsg.BLE_FSM_ON_CHARACTERISTIC_NOTIFY_MSG.ordinal(), bundle);
        }

        @Override
        public void onDescriptorRead(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            logger.d( "onDescriptorRead status:" + status);
            Bundle bundle = new Bundle();
            byte[] data = new byte[descriptor.getValue().length];
            System.arraycopy(descriptor.getValue(), 0, data, 0, descriptor.getValue().length);
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_READ_DESCRIPTOR.ordinal());
            bundle.putParcelable(EXTRA_SERVICE_UUID, new ParcelUuid(descriptor.getCharacteristic().getService().getUuid()));
            bundle.putParcelable(EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(descriptor.getCharacteristic().getUuid()));
            bundle.putParcelable(EXTRA_DESCRIPTOR_UUID, new ParcelUuid(descriptor.getUuid()));
            bundle.putByteArray(EXTRA_VALUE_DATA, data);
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }

        @Override
        public void onDescriptorWrite(BluetoothGatt gatt, BluetoothGattDescriptor descriptor, int status) {
            logger.d( "onDescriptorWrite: status:" + status);
            Bundle bundle = new Bundle();
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_WRITE_DESCRIPTOR.ordinal());
            bundle.putParcelable(EXTRA_SERVICE_UUID, new ParcelUuid(descriptor.getCharacteristic().getService().getUuid()));
            bundle.putParcelable(EXTRA_CHARACTERISTIC_UUID, new ParcelUuid(descriptor.getCharacteristic().getUuid()));
            bundle.putParcelable(EXTRA_DESCRIPTOR_UUID, new ParcelUuid(descriptor.getUuid()));
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }

        @Override
        public void onReliableWriteCompleted(BluetoothGatt gatt, int status) {
            logger.d( "onReliableWriteCompleted: status:" + status);
            sendMessage(BleFsmMsg.BLE_FSM_ON_RELIABLE_WRITE_COMPLETE_MSG.ordinal(), status, 0, null);
        }

        @Override
        public void onReadRemoteRssi(BluetoothGatt gatt, int rssi, int status) {
            //logger.d( "onReadRemoteRssi: status: " + status + " rssi:" + rssi);
            Bundle bundle = new Bundle();
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_READ_RSSI.ordinal());
            bundle.putInt(EXTRA_RSSI_VALUE, rssi);
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }

        @Override
        public void onMtuChanged(BluetoothGatt gatt, int mtu, int status) {
            logger.d( "onMtuChanged: status:" + status + " mtu:" + mtu);
            Bundle bundle = new Bundle();
            bundle.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_REQUEST_MTU.ordinal());
            bundle.putInt(EXTRA_MTU_VALUE, mtu);
            sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), status, 0, bundle);
        }
    };

    public interface BleDeviceFsmCallback {
        void onInited();
        void onScanStateChanged(int state);
        void onConnectStateChanged(int state, int reason);
        void onServiceDiscoveryState(int state);
        void onVerifying();
        void onReady();
        void onError(int errorCause);
        void onCharacteristiChanged(UUID serviceUUID, UUID characteristicUUID, byte[] data);
        void onReliableWriteCompleted(int status);
        void onFsmStopped();
    }

    class InitState extends State {
        @Override
        public void enter() {
            logger.d( "enter: InitState");
            connectSate = BluetoothProfile.STATE_DISCONNECTED;
            disconnectedByPurpose = false;

            if (bleDevice != null) {
                bleDevice.onInited();
            }

            isWorking = true;
        }

        @Override
        public void exit() {
            logger.d( "exit: InitState");
            isWorking = false;
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
            logger.d( "processMessage: initState:" + fsmMsg);

            switch (fsmMsg) {
                case BLE_FSM_START_FSM_MSG: {
                    int shouldScan = msg.arg1;

                    if(shouldScan != 0) {
                        deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_SCAN_MSG.ordinal()));
                        transitionTo(scanningState);
                    }else {
                        deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_CONNECT_MSG.ordinal()));
                        transitionTo(connectingState);
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_STOP_FSM_MSG: {
                    deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_STOP_FSM_MSG.ordinal()));
                    transitionTo(stoppingState);
                    ret = true;
                }
                break;
            }
            return ret;
        }
    }

    class StoppingState extends State {
        private Timer waitDisconnectedTimer;

        @Override
        public void enter() {
            logger.d( "enter: StoppingState");
            if (connectSate != BluetoothProfile.STATE_DISCONNECTED) {
                waitDisconnectedTimer = new Timer();
                waitDisconnectedTimer.schedule(new TimerTask() {
                    @Override
                    public void run() {
                        sendMessage(BleFsmMsg.BLE_FSM_WAIT_FOR_DISCONNECT_TIMEOUT_MSG.ordinal());
                    }
                }, 2000);
            }else {
                quitNow();
                transitionToHaltingState();
            }
        }

        @Override
        public void exit() {
            logger.d( "exit: StoppingState");
            if (waitDisconnectedTimer != null) {
                waitDisconnectedTimer.cancel();
                waitDisconnectedTimer = null;
            }
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
            logger.d( "processMessage: StoppingState：" + fsmMsg);
            switch (fsmMsg) {
                case BLE_FSM_STOP_FSM_MSG: {
                    if (connectSate == BluetoothProfile.STATE_DISCONNECTED) {
                        if (bluetoothGatt != null) {
                            try {
                                bluetoothGatt.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }

                            bluetoothGatt = null;
                        }

                        quitNow();
                        transitionToHaltingState();
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_CONNECTION_STATE_CHANGED_MSG: {
                    int state = msg.arg1;

                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
                        if (bluetoothGatt != null) {
                            try {
                                bluetoothGatt.close();
                            } catch (Exception e) {
                                e.printStackTrace();
                            }

                            bluetoothGatt = null;
                        }
                        quitNow();
                        transitionToHaltingState();
                    }

                    notifyDeviceConnectChanged(state);

                    ret = true;
                }
                break;

                case BLE_FSM_WAIT_FOR_DISCONNECT_TIMEOUT_MSG: {
                    if (bluetoothGatt != null) {
                        try {
                            bluetoothGatt.close();
                        } catch (Exception e) {
                            e.printStackTrace();
                        }

                        bluetoothGatt = null;
                    }
                    quitNow();
                    transitionToHaltingState();
                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class ScanningState extends State {
        private TimeoutMonitor monitor;
        private Object scanCallbackHolder;
        private boolean isScanning;

        @TargetApi(Build.VERSION_CODES.M)
        private void startScanWithNewApi() {
            logger.d( "startScanWithNewApi: ");
            ScanCallback callback = new ScanCallback() {
                @Override
                public void onScanResult(int callbackType, ScanResult result) {

                    if (bleDevice != null && !stopScanning) {
                        logger.d("onScanResult: btdevice address:" + bleDevice.getMac());
                        logger.d("scan name:" + bleDevice.getName());
                        if (result.getDevice().getAddress().equals(bleDevice.getMac())) {
                            if (result.getScanRecord() != null) {
                                bleDevice.setScanRecord(result.getScanRecord().getBytes());
                                sendMessage(BleFsmMsg.BLE_FSM_STOP_SCAN_MSG.ordinal());
                                stopScanning = true;
                                sendMessage(BleFsmMsg.BLE_FSM_SCAN_FOUND_MSG.ordinal());
                            }
                        }
                    }
                }

                @Override
                public void onBatchScanResults(List<ScanResult> results) {
                    logger.d( "onBatchScanResults: ");
                    for(ScanResult scanResult: results) {
                        if (bleDevice != null && !stopScanning) {
                            if (scanResult.getDevice().getAddress().equals(bleDevice.getMac())) {
                                if (scanResult.getScanRecord() != null) {
                                    bleDevice.setScanRecord(scanResult.getScanRecord().getBytes());
                                    logger.d("scan name:" + bleDevice.getName());
                                    sendMessage(BleFsmMsg.BLE_FSM_STOP_SCAN_MSG.ordinal());
                                    stopScanning = true;
                                    sendMessage(BleFsmMsg.BLE_FSM_SCAN_FOUND_MSG.ordinal());
                                    break;
                                }
                            }
                        }
                    }
                }

                @Override
                public void onScanFailed(int errorCode) {
                    logger.d( "onScanFailed: " + errorCode);
                }
            };

            if (bleDevice != null) {
                List<ScanFilter> list = new ArrayList<>();
                ScanFilter scanFilter = new ScanFilter.Builder().setDeviceAddress(bleDevice.getMac()).build();
                list.add(scanFilter);
                int scanMode = ScanSettings.SCAN_MODE_BALANCED;
                if (curScanMode == LOW_LATENCY_SCAN_MODE) {
                    scanMode = ScanSettings.SCAN_MODE_LOW_LATENCY;
                } else if (curScanMode == BALANCED_SCAN_MODE) {
                    scanMode = ScanSettings.SCAN_MODE_BALANCED;
                } else if (curScanMode == LOW_POWER_SCAN_MODE) {
                    scanMode = ScanSettings.SCAN_MODE_LOW_POWER;
                }
                BleHelper.getInstance().startScanLeBluetooth(list, ScanSettings.CALLBACK_TYPE_ALL_MATCHES, callback, scanMode);
                scanCallbackHolder = callback;
            }
        }

        @TargetApi(Build.VERSION_CODES.M)
        private void stopScanWithNewApi() {
            BleHelper.getInstance().stopScanLeBluetooth((ScanCallback)scanCallbackHolder);
        }

        @TargetApi(Build.VERSION_CODES.KITKAT)
        private void startScanWithOldApi() {
            BluetoothAdapter.LeScanCallback leScanCallback = new BluetoothAdapter.LeScanCallback() {
                @Override
                public void onLeScan(BluetoothDevice device, int rssi, byte[] scanRecord) {
                    if(bleDevice != null && !stopScanning && device.getAddress().equals(bleDevice.getMac())) {
                        bleDevice.setScanRecord(scanRecord);
                        logger.d("scan name:" + bleDevice.getName());
                        sendMessage(BleFsmMsg.BLE_FSM_STOP_SCAN_MSG.ordinal());
                        stopScanning = true;
                        sendMessage(BleFsmMsg.BLE_FSM_SCAN_FOUND_MSG.ordinal());
                    }
                }
            };

            BleHelper.getInstance().startScanLeBluetooth(leScanCallback);
            scanCallbackHolder = leScanCallback;
        }

        @TargetApi(Build.VERSION_CODES.KITKAT)
        private void stopScanWithOldApi() {
            BleHelper.getInstance().stopScanLeBluetooth((BluetoothAdapter.LeScanCallback)scanCallbackHolder);
        }


        @Override
        public void enter() {
            logger.d( "enter: ScanningState");
            isScanning = false;
            if (bleDevice != null) {
                monitor = new TimeoutMonitor(bleDevice.getScanTimeout(), bleDevice.getScanTryTimes(), 1000, null, new TimeoutMonitor.MonitorCallback() {
                    @Override
                    public void doAction(Bundle arg) {
                        sendMessage(BleFsmMsg.BLE_FSM_SCAN_RETRY_MSG.ordinal());
                    }

                    @Override
                    public void onTimeout() {
                        sendMessage(BleFsmMsg.BLE_FSM_STOP_SCAN_MSG.ordinal());
                        stopScanning = true;
                        sendMessage(BleFsmMsg.BLE_FSM_SCAN_TIMEOUT_MSG.ordinal());
                    }
                });
            }
        }

        private boolean startScan() {
            if (!BleHelper.getInstance().isBluetoothOn()) {
                return false;
            }

            stopScanning = false;

            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                startScanWithNewApi();
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                startScanWithOldApi();
            } else {
                if (bleDevice != null) {
                    bleDevice.onError(BleErrorCause.BLE_ERR_NOT_SUPPORT_BLE.ordinal());
                }

                return false;
            }

            if (bleDevice != null) {
                bleDevice.onScanStateChanged(STATE_STARTING);
            }
            return true;
        }

        private boolean stopScan() {
            if (!BleHelper.getInstance().isBluetoothOn()) {
                return false;
            }
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                stopScanWithNewApi();
            } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
                stopScanWithOldApi();
            } else {
                // telling that ble is not support for these version
                if (bleDevice != null) {
                    bleDevice.onError(BleErrorCause.BLE_ERR_NOT_SUPPORT_BLE.ordinal());
                }

                return false;
            }

            if (bleDevice != null) {
                bleDevice.onScanStateChanged(STATE_STOP);
            }

            return true;
        }

        @Override
        public void exit() {
            logger.d( "exit: ScanningState");

            if (isScanning) {
                stopScan();
            }

            if (monitor != null) {
                monitor.stopRepeat();
                monitor.stopMonitor();
                monitor = null;
            }

            scanCallbackHolder = null;
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
            logger.d( "processMessage: ScanningState:" + fsmMsg);

            switch (fsmMsg) {
                case BLE_FSM_START_SCAN_MSG:
                    if (monitor != null) {
                        monitor.resetStatus();
                        monitor.startMonitor();
                    }

                    if (isScanning) {
                        ret = true;
                        break;
                    }
                case BLE_FSM_SCAN_RETRY_MSG: {
                    if (!startScan() && bleDevice != null) {
                        if (isFullReconnecting) {
                            currentFullConnectTry = bleDevice.getFullConnectTryTimes();
                            currentFullConnectTried = 0;
                            curScanMode = LOW_LATENCY_SCAN_MODE;
                            bleDevice.onError(BleErrorCause.BLE_ERR_CONNECT_BLE_TIMEOUT.ordinal());
                            isFullReconnecting = false;
                        }
                        transitionTo(disconnectedState);
                    }
                    isScanning = true;
                    ret = true;
                }
                break;

                case BLE_FSM_SCAN_TIMEOUT_MSG: {
                    if (!monitor.doRepeat()) {
                       if (bleDevice != null) {
                           logger.d( "BLE_FSM_SCAN_TIMEOUT_MSG: isFullReconnecting:" + isFullReconnecting);
                           if (isScanning) {
                               stopScan();
                               isScanning = false;
                           }

                           if (!isFullReconnecting) {
                               bleDevice.onError(BleErrorCause.BLE_ERR_REACH_MAX_SCAN_TRY.ordinal());
                               transitionTo(disconnectedState);
                           }else {
                               logger.d( "BLE_FSM_SCAN_TIMEOUT_MSG: currentFullConnectTry:" + currentFullConnectTry);
                               if (currentFullConnectTry > 0) {
                                   currentFullConnectTry--;
                                   currentFullConnectTried++;
                                   if (currentFullConnectTried < 2) {
                                       curScanMode = LOW_LATENCY_SCAN_MODE;
                                   }else if (currentFullConnectTried < 6) {
                                       curScanMode = BALANCED_SCAN_MODE;
                                   }else {
                                       curScanMode = LOW_POWER_SCAN_MODE;
                                   }

                                   sendMessage(BleFsmMsg.BLE_FSM_START_SCAN_MSG.ordinal());
                               }else {
                                   currentFullConnectTry = bleDevice.getFullConnectTryTimes();
                                   currentFullConnectTried = 0;
                                   curScanMode = LOW_LATENCY_SCAN_MODE;
                                   bleDevice.onError(BleErrorCause.BLE_ERR_CONNECT_BLE_TIMEOUT.ordinal());
                                   isFullReconnecting = false;
                                   transitionTo(disconnectedState);
                               }
                           }
                       }
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_STOP_SCAN_MSG: {
                    stopScan();
                    isScanning = false;
                    ret = true;
                }
                break;

                case BLE_FSM_SCAN_FOUND_MSG: {
                    sendMessage(BleFsmMsg.BLE_FSM_START_CONNECT_MSG.ordinal());
                    transitionTo(connectingState);

                    if (bleDevice != null) {
                        bleDevice.onScanStateChanged(STATE_FOUND);
                    }

                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class ConnectingState extends State {
        private TimeoutMonitor monitor;

        @Override
        public void enter() {
            logger.d( "enter: ConnectingState");

            if (bleDevice != null) {
                monitor = new TimeoutMonitor(bleDevice.getConnectingTimeout(), bleDevice.getConnectTryTimes(), 1000, null, new TimeoutMonitor.MonitorCallback() {
                    @Override
                    public void doAction(Bundle arg) {
                        sendMessage(BleFsmMsg.BLE_FSM_CONNECT_RETRY_NO_SCAN_MSG.ordinal());
                    }

                    @Override
                    public void onTimeout() {
                        sendMessage(BleFsmMsg.BLE_FSM_CONNECT_TIMEOUT_MSG.ordinal());
                    }
                });
            }
        }

        @Override
        public void exit() {
            logger.d( "exit: ConnectingState");
            if (monitor != null) {
                monitor.stopMonitor();
                monitor.stopRepeat();
                monitor = null;
            }
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }

            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];

            switch (fsmMsg) {
                case BLE_FSM_START_CONNECT_MSG:
                    if (bleDevice != null) {
                        currentFullConnectTry = bleDevice.getFullConnectTryTimes();
                    }
                    currentFullConnectTried = 0;
                    curScanMode = LOW_LATENCY_SCAN_MODE;
                    monitor.startMonitor();
                case BLE_FSM_CONNECT_RETRY_NO_SCAN_MSG: {
                    if (bluetoothGatt != null) {
                        try {
                            bluetoothGatt.close();
                        }catch (Exception e) {
                            e.printStackTrace();
                        }

                        try {
                            Thread.sleep(200); // Is 200 ms enough?
                        } catch (final InterruptedException e) {
                            // Ignore
                        }
                    }

                    if (bleDevice != null) {
                        bluetoothGatt = BleHelper.getInstance().connectBleDevice(bleDevice.getBluetoothDevice(), bluetoothGattCallback);
                        if (bluetoothGatt == null) {
                            bleDevice.onError(BleErrorCause.BLE_ERR_CONNECT_BLE_FAILED.ordinal());
                            transitionTo(disconnectedState);
                        }
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_CONNECT_RETRY_WITH_SCAN_MSG: {
                    logger.d( "BLE_FSM_CONNECT_RETRY_WITH_SCAN_MSG: isFullReconnecting:" + isFullReconnecting);
                    if (currentFullConnectTry > 0) {
                        currentFullConnectTry--;
                        currentFullConnectTried++;
                        if (currentFullConnectTried < 4) {
                            curScanMode = LOW_LATENCY_SCAN_MODE;
                        }else if (currentFullConnectTried < 10) {
                            curScanMode = BALANCED_SCAN_MODE;
                        }else {
                            curScanMode = LOW_POWER_SCAN_MODE;
                        }
                        deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_SCAN_MSG.ordinal()));
                        transitionTo(scanningState);
                    }else {
                        if (bleDevice != null) {
                            bleDevice.onError(BleErrorCause.BLE_ERR_CONNECT_BLE_TIMEOUT.ordinal());
                            currentFullConnectTry = bleDevice.getFullConnectTryTimes();
                            currentFullConnectTried = 0;
                            curScanMode = LOW_LATENCY_SCAN_MODE;
                        }
                        isFullReconnecting = false;
                        transitionTo(disconnectedState);
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_CONNECT_TIMEOUT_MSG: {
                    if (!monitor.doRepeat()) {
                        isFullReconnecting = true;
                        sendMessage(BleFsmMsg.BLE_FSM_CONNECT_RETRY_WITH_SCAN_MSG.ordinal());
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_STOP_CONNECT_MSG: {
                    disconnectedByPurpose = true;
                    if (bluetoothGatt != null) {
                        bluetoothGatt.disconnect();
                    }

                    transitionTo(disconnectedState);
                    ret = true;
                }
                break;

                case BLE_FSM_CONNECTION_STATE_CHANGED_MSG: {
                    int state = msg.arg1;

                    if (state == BluetoothProfile.STATE_CONNECTED) {
                        transitionTo(connectedState);
                    }

                    notifyDeviceConnectChanged(state);

                    ret = true;
                }
                break;

                case BLE_FSM_STOP_FSM_MSG: {
                    if (bluetoothGatt != null) {
                        bluetoothGatt.disconnect();
                    }
                }
                break;
            }

            return ret;
        }
    }

    class ConnectedState extends State {
        TimeoutMonitor monitor;

        @Override
        public void enter() {
            logger.d( "enter: ConnectedState");
            disconnectedByPurpose = false;
            isFullReconnecting = false;
            isConnected = true;
            sendMessageDelayed(obtainMessage(BleFsmMsg.BLE_FSM_START_DISCOVERY_SERVICE_MSG.ordinal()), 500);
        }

        @Override
        public void exit() {
            logger.d( "exit: ConnectedState");
            if (monitor != null) {
                monitor.stopMonitor();
                monitor.stopRepeat();
                monitor = null;
            }

            if (bluetoothGatt != null) {
                try {
                    bluetoothGatt.disconnect();
                }catch (Exception e) {
                    e.printStackTrace();
                }
            }

            isConnected = false;
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
            logger.d( "processMessage: ConnectedMsg:" + fsmMsg);

            switch (fsmMsg) {
                case BLE_FSM_START_DISCOVERY_SERVICE_MSG:
                    monitor = new TimeoutMonitor(5000, 1, 1000, null, new TimeoutMonitor.MonitorCallback() {
                        @Override
                        public void doAction(Bundle arg) {
                            sendMessage(BleFsmMsg.BLE_FSM_DISCOVERY_SERVICE_RETRY_MSG.ordinal());
                        }

                        @Override
                        public void onTimeout() {
                            sendMessage(BleFsmMsg.BLE_FSM_DISCOVERY_SERVICE_TIMEOUT_MSG.ordinal());
                        }
                    });
                    monitor.startMonitor();
                case BLE_FSM_DISCOVERY_SERVICE_RETRY_MSG: {
                    if (bleDevice != null) {
                        if (bluetoothGatt != null) {
                            if (!bluetoothGatt.discoverServices()) {
                                bleDevice.onError(BleErrorCause.BLE_ERR_DISCOVERY_SERVICE_FAILED.ordinal());
                                bleDevice.onServiceDiscoveryState(STATE_FAIL);
                            } else {
                                bleDevice.onServiceDiscoveryState(STATE_TRYING);
                            }
                        } else {
                            bleDevice.onError(BleErrorCause.BLE_ERR_NOT_CONNECTED.ordinal());
                            bleDevice.onServiceDiscoveryState(STATE_FAIL);
                        }
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_DISCOVERY_SERVICE_FINISH_MSG: {
                    int status = msg.arg1;
                    if (bleDevice != null) {
                        if (monitor != null) {
                            monitor.stopMonitor();
                            monitor.stopRepeat();
                            monitor = null;
                        }

                        if (status == BluetoothGatt.GATT_SUCCESS) {
                            if (bleDevice != null) {
                                bleDevice.onServiceDiscoveryState(STATE_FOUND);
                                if (bleDevice.isNeedVerified()) {
                                    deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_VERIFY_DEVICE_MSG.ordinal()));
                                    transitionTo(verifyingState);
                                } else {
                                    transitionTo(readyState);
                                }
                            }
                        }else {
                            if (bleDevice != null) {
                                bleDevice.onServiceDiscoveryState(STATE_FAIL);
                            }
                        }
                    }
                    ret = true;
                }
                break;

                case BLE_FSM_DISCOVERY_SERVICE_TIMEOUT_MSG: {
                    if(monitor != null && !monitor.doRepeat()) {
                        if (monitor != null) {
                            monitor.stopMonitor();
                            monitor.stopRepeat();
                            monitor = null;
                        }
                        if (bleDevice != null) {
                            bleDevice.onError(BleErrorCause.BLE_ERR_DISCOVERY_SERVICE_TIMEOUT.ordinal());
                            bleDevice.onServiceDiscoveryState(STATE_FAIL);
                        }
                    }
                }
                break;

                case BLE_FSM_CONNECTION_STATE_CHANGED_MSG: {
                    int state = msg.arg1;

                    if (state == BluetoothProfile.STATE_DISCONNECTED) {
                        if (!disconnectedByPurpose) {
                            if (bleDevice != null && bleDevice.isAutoConnect()) {
                                isFullReconnecting = true;
                                currentFullConnectTry = bleDevice.getFullConnectTryTimes();
                                currentFullConnectTried = 0;
                                curScanMode = LOW_LATENCY_SCAN_MODE;
                                deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_CONNECT_MSG.ordinal()));
                            }
                        }
                        transitionTo(disconnectedState);
                    }

                    notifyDeviceConnectChanged(state);
                    ret = true;
                }
                break;

                case BLE_FSM_STOP_CONNECT_MSG: {
                    disconnectedByPurpose = true;
                    if (bluetoothGatt != null) {
                        bluetoothGatt.disconnect();
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_RECONNECT_MSG: {
                    if (bluetoothGatt != null) {
                        bluetoothGatt.disconnect();
                    }

                    ret = true;
                }
            }

            return ret;
        }
    }

    class DataProcessState extends State {
        private BleAction curBleAction;
        private Timer executionTimer;

        @Override
        public void enter() {
            curBleAction = null;
            bleActionQueue = new LinkedList<>();
        }

        @Override
        public void exit() {
            if (executionTimer != null) {
                executionTimer.cancel();
                executionTimer = null;
            }

            bleActionQueue.clear();
            bleActionQueue = null;
            curBleAction = null;
        }

        /**
         * 有一些设备是write with no response，有一些是write。最好能判断characteristic的写入类型来判断那种
         *
         */
        private boolean processBleAction(BleAction action) {
            BleAction.BleCmd cmd = action.cmd;
            boolean ret = false;
            logger.d( "processBleAction: cmd:" + cmd);

            switch (cmd) {
                case BLE_CMD_WRITE_RELIABLE: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    byte[] data = bundle.getByteArray(EXTRA_RAW_DATA);
                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);

                        if (characteristic != null) {
                            characteristic.setValue(data);
                            characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_DEFAULT);
                            ret = bluetoothGatt.writeCharacteristic(characteristic);
                        }
                    }
                }
                break;

                case BLE_CMD_WRITE_DATA: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    byte[] data = bundle.getByteArray(EXTRA_RAW_DATA);

                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            logger.d("write type of " + characteristicUUID.toString() + " is " + characteristic.getWriteType());
                            characteristic.setValue(data);
                            characteristic.setWriteType(BluetoothGattCharacteristic.WRITE_TYPE_NO_RESPONSE);
                            ret = bluetoothGatt.writeCharacteristic(characteristic);
                        }
                    }
                }
                break;

                case BLE_CMD_READ_DATA: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();

                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            ret = bluetoothGatt.readCharacteristic(characteristic);
                        }
                    }
                }
                break;

                case BLE_CMD_READ_DESCRIPTOR: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    UUID descriptorUuid = ((ParcelUuid) bundle.getParcelable(EXTRA_DESCRIPTOR_UUID)).getUuid();

                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUuid);
                            if (descriptor != null) {
                                ret = bluetoothGatt.readDescriptor(descriptor);
                            }
                        }
                    }
                }
                break;

                case BLE_CMD_ENABLE_INDICATION: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    boolean enable = bundle.getBoolean(EXTRA_ENABLE_VALUE);
                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            if (bluetoothGatt.setCharacteristicNotification(characteristic, enable)) {
                                final UUID descriptorConfigurationUUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
                                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorConfigurationUUID);
                                logger.d( "processBleAction: descriptor:" + descriptor);
                                if (descriptor != null) {
                                    if (enable) {
                                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_INDICATION_VALUE);
                                    } else {
                                        descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                                    }

                                    ret = bluetoothGatt.writeDescriptor(descriptor);
                                } else {
                                    Bundle result = new Bundle();
                                    result.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_WRITE_DESCRIPTOR.ordinal());
                                    sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), BluetoothGatt.GATT_SUCCESS, 0, result);
                                    ret = true;
                                }
                            }
                        }
                    }
                }
                break;

                case BLE_CMD_ENABLE_NOTIFICATION: {
                    Bundle bundle = action.cmdArg;
                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    boolean enable = bundle.getBoolean(EXTRA_ENABLE_VALUE);

                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            if (bluetoothGatt.setCharacteristicNotification(characteristic, enable)) {
                                final UUID descriptorConfigurationUUID = UUID.fromString("00002902-0000-1000-8000-00805f9b34fb");
                                BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorConfigurationUUID);
                                if (descriptor != null) {
                                    if (enable) {
                                        descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE);
                                    } else {
                                        descriptor.setValue(BluetoothGattDescriptor.DISABLE_NOTIFICATION_VALUE);
                                    }

                                    ret = bluetoothGatt.writeDescriptor(descriptor);
                                } else {
                                    Bundle result = new Bundle();
                                    result.putInt(EXTRA_ACTION_VALUE, BleAction.BleCmd.BLE_CMD_WRITE_DESCRIPTOR.ordinal());
                                    sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_RESULT_MSG.ordinal(), BluetoothGatt.GATT_SUCCESS, 0, result);
                                    ret = true;
                                }
                            }
                        }
                    }
                }
                break;

                case BLE_CMD_WRITE_DESCRIPTOR: {
                    Bundle bundle = action.cmdArg;

                    UUID characteristicUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID)).getUuid();
                    UUID serviceUUID = ((ParcelUuid) bundle.getParcelable(EXTRA_SERVICE_UUID)).getUuid();
                    UUID descriptorUuid = ((ParcelUuid) bundle.getParcelable(EXTRA_DESCRIPTOR_UUID)).getUuid();
                    byte[] data = bundle.getByteArray(EXTRA_RAW_DATA);

                    if (bleDevice != null) {
                        BluetoothGattCharacteristic characteristic = bleDevice.getCharacteristic(serviceUUID, characteristicUUID);
                        if (characteristic != null) {
                            BluetoothGattDescriptor descriptor = characteristic.getDescriptor(descriptorUuid);
                            if (descriptor != null) {
                                descriptor.setValue(data);
                                ret = bluetoothGatt.writeDescriptor(descriptor);
                            }
                        }
                    }
                }
                break;

                case BLE_CMD_READ_RSSI: {
                    if (bluetoothGatt != null) {
                        ret = bluetoothGatt.readRemoteRssi();
                    }
                }
                break;

                case BLE_CMD_REQUEST_MTU: {
                    Bundle bundle = action.cmdArg;
                    int mtu = bundle.getInt(EXTRA_MTU_VALUE);
                    if(Build.VERSION.SDK_INT > Build.VERSION_CODES.LOLLIPOP) {
                        ret = bluetoothGatt.requestMtu(mtu);
                    }
                }
                break;
            }

            return ret;
        }

        private BleAction.BleCmd getRealTriggerAction(BleAction.BleCmd cmd) {
            BleAction.BleCmd realCmd;

            switch (cmd) {
                case BLE_CMD_ENABLE_INDICATION:
                case BLE_CMD_ENABLE_NOTIFICATION: {
                    realCmd = BleAction.BleCmd.BLE_CMD_WRITE_DESCRIPTOR;
                }
                break;

                case BLE_CMD_WRITE_DATA:
                case BLE_CMD_WRITE_RELIABLE: {
                    realCmd = BleAction.BleCmd.BLE_CMD_WRITE_DATA;
                }
                break;

                default: {
                    realCmd = cmd;
                }
                break;
            }

            return realCmd;
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
            //logger.d( "processMessage: DataProcessState:" + fsmMsg);

            switch (fsmMsg) {
                case BLE_FSM_ON_RELIABLE_WRITE_COMPLETE_MSG: {
                    if (bleDevice != null) {
                        bleDevice.onReliableWriteCompleted(msg.arg1);
                    }
                }
                break;

                case BLE_FSM_ON_CHARACTERISTIC_NOTIFY_MSG: {
                    Bundle bundle = (Bundle)msg.obj;
                    ParcelUuid serviceUUID = bundle.getParcelable(EXTRA_SERVICE_UUID);
                    ParcelUuid characteristicUUID = bundle.getParcelable(EXTRA_CHARACTERISTIC_UUID);
                    byte[] data = bundle.getByteArray(EXTRA_VALUE_DATA);
                    if (bleDevice != null && serviceUUID != null && characteristicUUID != null) {
                        bleDevice.onCharacteristiChanged(serviceUUID.getUuid(), characteristicUUID.getUuid(), data);
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_ADD_ACTION_TO_QUEUE_MSG: {
                    bleActionQueue.offer((BleAction) msg.obj);

                    if (curBleAction == null && !bleActionQueue.isEmpty()) {
                        sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_MSG.ordinal());
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_EXECUTE_ACTION_MSG: {
                    logger.d( "processMessage: action queue length:" + bleActionQueue.size());
                    if (!bleActionQueue.isEmpty() && curBleAction == null) {
                        curBleAction = bleActionQueue.poll();
                        logger.d( "processMessage: left:" + bleActionQueue.size());

                        if (executionTimer != null) {
                            executionTimer.cancel();
                            executionTimer = null;
                        }

                        if (!processBleAction(curBleAction)) {
                            if (curBleAction.callback != null) {
                                curBleAction.callback.onActionResult(BluetoothGatt.GATT_FAILURE, null);
                                curBleAction = null;
                                sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_MSG.ordinal());
                            }

                            if (bleDevice != null) {
                                bleDevice.onError(BleErrorCause.BLE_ERR_EXECUTE_ACTION_FAILED.ordinal());
                            }
                        }else {
                            executionTimer = new Timer();
                            executionTimer.schedule(new TimerTask() {
                                @Override
                                public void run() {
                                    sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_TIMEOUT_MSG.ordinal());
                                }
                            }, 5000);
                        }
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_EXECUTE_ACTION_TIMEOUT_MSG: {
                    if (executionTimer != null) {
                        executionTimer.cancel();
                        executionTimer = null;
                    }

                    if (bleDevice != null) {
                        bleDevice.onError(BleErrorCause.BLE_ERR_EXECUTE_ACTION_TIMEOUT.ordinal());
                    }

                    if (curBleAction != null) {
                        if (curBleAction.callback != null) {
                            curBleAction.callback.onActionResult(BluetoothGatt.GATT_FAILURE, null);
                        }
                        curBleAction = null;
                        sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_MSG.ordinal());
                    }

                    ret = true;
                }
                break;

                case BLE_FSM_EXECUTE_ACTION_RESULT_MSG: {
                    Bundle bundle = (Bundle) msg.obj;
                    BleAction.BleCmd curCmd = BleAction.BleCmd.values()[bundle.getInt(EXTRA_ACTION_VALUE)];

                    logger.d( "processMessage: curCmd:" + curCmd + " status:" + msg.arg1);

                    if (executionTimer != null) {
                        executionTimer.cancel();
                        executionTimer = null;
                    }

                    if (curBleAction != null) {
                        if (getRealTriggerAction(curBleAction.cmd) == curCmd) {
                            if (curBleAction.callback != null) {
                                curBleAction.callback.onActionResult(msg.arg1, (Bundle) msg.obj);
                            }
                        }else {
                            if (bleDevice != null) {
                                bleDevice.onError(BleErrorCause.BLE_ERR_EXECUTE_ACTION_FAILED.ordinal());
                            }
                        }

                        curBleAction = null;
                    }

                    sendMessage(BleFsmMsg.BLE_FSM_EXECUTE_ACTION_MSG.ordinal());

                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    class VerifyingState extends DataProcessState {
        @Override
        public void enter() {
            logger.d( "enter: VerifyingState");
            super.enter();
        }

        @Override
        public void exit() {
            logger.d( "exit: VerifyingState");
            super.exit();
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            ret = super.processMessage(msg);
            logger.d( "processMessage: VerifyingState super process result:" + ret);

            if (!ret) {
                if (msg.what < 0) {
                    return ret;
                }
                BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];
                logger.d( "processMessage: VerifyingState:" + fsmMsg);
                switch (fsmMsg) {
                    case BLE_FSM_START_VERIFY_DEVICE_MSG: {
                        if (bleDevice != null) {
                            bleDevice.onVerifying();
                        }

                        ret = true;
                    }
                    break;

                    case BLE_FSM_VERIFY_RESULT_MSG: {
                        int result = msg.arg1;
                        String message = (String) msg.obj;

                        if (result == 0) {
                            transitionTo(readyState);
                        }else {
                            if (message != null) {
                                bleDevice.onError(BleErrorCause.BLE_ERR_VERIFY_FAILED.ordinal());
                            }

                            sendMessage(BleFsmMsg.BLE_FSM_STOP_CONNECT_MSG.ordinal());
                        }

                        ret = true;
                    }
                    break;
                }
            }

            return ret;
        }
    }

    class ReadyState extends DataProcessState {
        Timer requestRssiTimer;

        @Override
        public void enter() {
            logger.d( "enter: ReadyState");
            super.enter();

            if (bleDevice != null) {
                bleDevice.onReady();
            }
        }

        @Override
        public void exit() {
            logger.d( "exit: ReadyState");
            super.exit();

            if (requestRssiTimer != null) {
                requestRssiTimer.cancel();
                requestRssiTimer = null;
            }
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            ret = super.processMessage(msg);

            if (!ret) {
                if (msg.what < 0) {
                    return ret;
                }
                BleFsmMsg bleFsmMsg = BleFsmMsg.values()[msg.what];
                switch (bleFsmMsg) {
                    case BLE_FSM_REQUEST_RSSI_FREQUENT_MSG: {
                        final int delay = msg.arg1;
                        if (requestRssiTimer != null) {
                            requestRssiTimer.cancel();
                        }

                        requestRssiTimer = new Timer();
                        requestRssiTimer.schedule(new TimerTask() {
                            @Override
                            public void run() {
                                BleAction bleAction = new BleAction();
                                bleAction.callback = new BleAction.BleActionCallback() {
                                    @Override
                                    public void onActionResult(int status, Bundle result) {
                                        if (status == BluetoothGatt.GATT_SUCCESS) {
                                            int rssi = result.getInt(EXTRA_RSSI_VALUE);
                                            bleDevice.setCurrentRssi(rssi);
                                        }
                                        sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_REQUEST_RSSI_FREQUENT_MSG.ordinal(), delay));
                                    }
                                };
                                bleAction.cmd = BleAction.BleCmd.BLE_CMD_READ_RSSI;
                                bleAction.cmdArg = null;
                                sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_ADD_ACTION_TO_QUEUE_MSG.ordinal(), bleAction));
                            }
                        }, delay);

                        ret = true;
                    }
                    break;

                    case BLE_FSM_STOP_REQUEST_RSSI_MSG: {
                        if (requestRssiTimer != null) {
                            requestRssiTimer.cancel();
                        }

                        ret = true;
                    }
                    break;
                }
            }

            return ret;
        }
    }

    class DisconnectedState extends State {
        @Override
        public void enter() {
            logger.d( "enter: DisconnectedState");
        }

        @Override
        public void exit() {
            logger.d( "exit: DisconnectedState");
        }

        @Override
        public boolean processMessage(Message msg) {
            boolean ret = false;
            if (msg.what < 0) {
                return ret;
            }
            BleFsmMsg fsmMsg = BleFsmMsg.values()[msg.what];

            switch (fsmMsg) {
                case BLE_FSM_CONNECTION_STATE_CHANGED_MSG: {
                    int state = msg.arg1;
                    notifyDeviceConnectChanged(state);
                }
                break;

                case BLE_FSM_START_CONNECT_MSG: {
                    if (BleHelper.getInstance().isBluetoothOn()) {
                        if (bleDevice.isFullReConnectProcessFirst()) {
                            deferMessage(obtainMessage(BleFsmMsg.BLE_FSM_START_SCAN_MSG.ordinal()));
                            transitionTo(scanningState);
                        }else {
                            deferMessage(msg);
                            transitionTo(connectingState);
                        }
                    }
                    ret = true;
                }
                break;
            }

            return ret;
        }
    }

    protected BleDeviceFsm(String name, BleDevice bleDevice) {
        super(name);
        this.bleDevice = bleDevice;

        initState = new InitState();
        scanningState = new ScanningState();
        connectingState = new ConnectingState();
        connectedState = new ConnectedState();
        verifyingState = new VerifyingState();
        readyState = new ReadyState();
        disconnectedState = new DisconnectedState();
        stoppingState = new StoppingState();

        addState(initState);
        addState(scanningState, initState);
        addState(connectingState, initState);
        addState(connectedState, initState);
        addState(verifyingState, connectedState);
        addState(readyState, connectedState);
        addState(disconnectedState, initState);
        addState(stoppingState);

        setInitialState(initState);
    }

    BluetoothGatt getBluetoothGatt() {
        return bluetoothGatt;
    }

    private void notifyDeviceConnectChanged(int state) {
        if (bleDevice != null) {
            int reason = 0;
            if (state == BluetoothProfile.STATE_DISCONNECTED) {
                reason = disconnectedByPurpose ? DISCONNECTED_IN_PURPOSE : DISCONNECTED_ACCIDENT;
            }
            bleDevice.onConnectStateChanged(state, reason);
        }
    }

    @Override
    protected void onQuitting() {
        logger.d("onQuitting......");
        if (bluetoothGatt != null) {
            try {
                bluetoothGatt.close();
            }catch (Exception e) {
                e.printStackTrace();
            }
            bluetoothGatt = null;
        }

        if (bleDevice != null) {
            bleDevice.onFsmStopped();
        }
    }

    public void startWorking(boolean shouldScan) {
        int withScan = shouldScan ? 1:0;
        sendMessage(BleFsmMsg.BLE_FSM_START_FSM_MSG.ordinal(), withScan);
    }

    public void stopWorking() {
        sendMessage(BleFsmMsg.BLE_FSM_STOP_FSM_MSG.ordinal());
    }

    public void connectDevice() {
        if (getCurrentState() != connectingState) {
            sendMessage(BleFsmMsg.BLE_FSM_START_CONNECT_MSG.ordinal());
        }
    }

    public void disconnectDevice() {
        sendMessage(BleFsmMsg.BLE_FSM_STOP_CONNECT_MSG.ordinal());
    }

    public void executeBleAction(BleAction action) {
        if (action != null) {
            sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_ADD_ACTION_TO_QUEUE_MSG.ordinal(), action));
        }
    }

    public boolean isWorking() {
        return isWorking;
    }

    public void reportVerifyResult(int result, String errorMessage) {
        sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_VERIFY_RESULT_MSG.ordinal(), result, 0, errorMessage));
    }

    public void requestRssiFrequently(int delay) {
        sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_REQUEST_RSSI_FREQUENT_MSG.ordinal(), delay));
    }

    public void stopRequestRssi() {
        sendMessage(obtainMessage(BleFsmMsg.BLE_FSM_STOP_REQUEST_RSSI_MSG.ordinal()));
    }

    public void detachBleDevice() {
        bleDevice = null;
    }

    public boolean isConnected() {
        return isConnected;
    }
}
